﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
using GameViewer.Core.Files;
using GameViewer.Core.Archives;

namespace Plugin_WoW.Files
{
    public class BLP : FileObj
    {
        class blp2header
        {
            public string ident;                       // "BLP2" magic number
            public int type;                           // Texture type: 0 = JPG, 1 = S3TC
            public byte compression;                   // Compression mode: 1 = raw, 2 = DXTC
            public byte alpha_bits;                    // 0, 1, or 8
            public byte alpha_type;                    // 0, 1, 7 or 8
            public byte has_mips;                      // 0 = no mips levels, 1 = has mips (number of levels determined by image size)
            public int width;                          // Image width in pixels
            public int height;                         // Image height in pixels
            public int[] mipmap_offsets = new int[16]; // The file offsets of each mipmap, 0 for unused
            public int[] mipmap_lengths = new int[16]; // The length of each mipmap data block

            public override string ToString()
            {
                return string.Format("id:{0} type:{1} compression:{2} alpha_bits:{3} alpha_type:{4}",
                    ident, type, compression, alpha_bits, alpha_type);
                //return base.ToString();
            }
        }

        private blp2header mHeader = new blp2header();
        List<Bitmap> mBitmaps = new List<Bitmap>();

        public BLP(FileInfo file)
            : base(file)
        {
        }

        public override void Initialize()
        {
            //读取文件头
            this.ReadHeader();
            
            //读取颜色表
            Color[] colors = new Color[256];
            for (int i = 0; i < 256; i++)
            {
                colors[i] = Color.FromArgb(mReader.ReadInt32());
            }

            //
            for (int i = 0; i < 16; i++)
            {
                int size = mHeader.mipmap_lengths[i];
                if (size == 0)
                {
                    break;
                }
                int offset = mHeader.mipmap_offsets[i];
                double fix = Math.Pow(mHeader.mipmap_lengths[0] / size, 0.5);
                int width = (int)(mHeader.width / fix);
                int height = (int)(mHeader.height / fix);

                if (width < 4 || height < 4)
                {
                    break;
                }

                mStream.Position = offset;

                var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                if (mHeader.compression == 1)
                {
                    // uncompressed, paletted, no alpha
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            byte rbg = mReader.ReadByte();
                            bmp.SetPixel(x, y, Color.FromArgb(255, colors[rbg].R, colors[rbg].G, colors[rbg].B));
                        }
                    }
                }
                else if ( mHeader.compression == 2 && (mHeader.alpha_bits == 1 || mHeader.alpha_bits == 0))
                {
                    // DXT1
                    for (int y = 0; y < height; y += 4)
                    {
                        for (int x = 0; x < width; x += 4)
                        {
                            short _High565 = mReader.ReadInt16();
                            short _Low565 = mReader.ReadInt16();                           
                            int _ColorLUT = mReader.ReadInt32();

                            int[,] ColorLUT = new int[4, 4] { { (_ColorLUT >> 30 & 3), (_ColorLUT >> 28 & 3), (_ColorLUT >> 26 & 3), (_ColorLUT >> 24 & 3) }, { (_ColorLUT >> 22 & 3), (_ColorLUT >> 20 & 3), (_ColorLUT >> 18 & 3), (_ColorLUT >> 16 & 3) }, { (_ColorLUT >> 14 & 3), (_ColorLUT >> 12 & 3), (_ColorLUT >> 10 & 3), (_ColorLUT >> 8 & 3) }, { (_ColorLUT >> 6 & 3), (_ColorLUT >> 4 & 3), (_ColorLUT >> 2 & 3), (_ColorLUT & 3) } };
                            Color[] RGBValues = new Color[4];
                            RGBValues[0] = Color.FromArgb(255, _High565 >> 8 & 255, _High565 >> 3 & 255, _High565 << 3 & 255);
                            RGBValues[1] = Color.FromArgb(255, _Low565 >> 8 & 255, _Low565 >> 3 & 255, _Low565 << 3 & 255);

                            if (RGBValues[0].ToArgb() > RGBValues[1].ToArgb())
                            {
                                RGBValues[2] = Color.FromArgb(255, 2 / 3 * RGBValues[0].R + 1 / 3 * RGBValues[1].R, 2 / 3 * RGBValues[0].G + 1 / 3 * RGBValues[1].G, 2 / 3 * RGBValues[0].B + 1 / 3 * RGBValues[1].B);
                                RGBValues[3] = Color.FromArgb(255, 1 / 3 * RGBValues[0].R + 2 / 3 * RGBValues[1].R, 1 / 3 * RGBValues[0].G + 2 / 3 * RGBValues[1].G, 1 / 3 * RGBValues[0].B + 2 / 3 * RGBValues[1].B);
                            }
                            else
                            {
                                RGBValues[2] = Color.FromArgb(255, 1 / 2 * RGBValues[0].R + 1 / 2 * RGBValues[1].R, 1 / 2 * RGBValues[0].G + 1 / 2 * RGBValues[1].G, 1 / 2 * RGBValues[0].B + 1 / 2 * RGBValues[1].B);
                                RGBValues[3] = Color.FromArgb(0, 0, 0, 0);
                            }

                            for (int y1 = 0; y1 <= 3; y1++)
                            {
                                for (int x1 = 0; x1 <= 3; x1++)
                                {
                                    int ci = ColorLUT[3 - y1, 3 - x1];
                                    bmp.SetPixel(x + x1, y + y1, Color.FromArgb(RGBValues[ci].A, RGBValues[ci].R, RGBValues[ci].G, RGBValues[ci].B));
                                }
                            }
                        }
                    }
                }
                else if ( mHeader.compression == 2 && mHeader.alpha_bits == 8 && (mHeader.alpha_type == 0 || mHeader.alpha_type == 1 || mHeader.alpha_type ==8))
                {
                    // DXT3
                    for (int y = 0; y < height; y += 4)
                    {
                        for (int x = 0; x < width; x += 4)
                        {
                            byte[] _AlphaMap = new byte[8] { 
                                mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte(), 
                                mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte() };
                            short[] _RGBValues = new short[2] { mReader.ReadInt16(), mReader.ReadInt16() };
                            int _ColorLUT = mReader.ReadInt32();

                            int[,] ColorLUT = new int[4, 4] { { (_ColorLUT >> 30 & 3), (_ColorLUT >> 28 & 3), (_ColorLUT >> 26 & 3), (_ColorLUT >> 24 & 3) }, { (_ColorLUT >> 22 & 3), (_ColorLUT >> 20 & 3), (_ColorLUT >> 18 & 3), (_ColorLUT >> 16 & 3) }, { (_ColorLUT >> 14 & 3), (_ColorLUT >> 12 & 3), (_ColorLUT >> 10 & 3), (_ColorLUT >> 8 & 3) }, { (_ColorLUT >> 6 & 3), (_ColorLUT >> 4 & 3), (_ColorLUT >> 2 & 3), (_ColorLUT & 3) } };

                            int[,] AlphaMap = new int[4, 4] { { _AlphaMap[0] << 4 | 15, _AlphaMap[0] | 15, _AlphaMap[1] << 4 | 15, _AlphaMap[1] | 15 }, { _AlphaMap[2] << 4 | 15, _AlphaMap[2] | 15, _AlphaMap[3] << 4 | 15, _AlphaMap[3] | 15 }, { _AlphaMap[4] << 4 | 15, _AlphaMap[4] | 15, _AlphaMap[5] << 4 | 15, _AlphaMap[5] | 15 }, { _AlphaMap[6] << 4 | 15, _AlphaMap[6] | 15, _AlphaMap[7] << 4 | 15, _AlphaMap[7] | 15 } };

                            Color[] RGBValues = new Color[4];
                            RGBValues[0] = Color.FromArgb(0, _RGBValues[0] >> 8 & 255, _RGBValues[0] >> 3 & 255, _RGBValues[0] << 3 & 255);
                            RGBValues[1] = Color.FromArgb(0, _RGBValues[1] >> 8 & 255, _RGBValues[1] >> 3 & 255, _RGBValues[1] << 3 & 255);

                            if (_RGBValues[0] > _RGBValues[1])
                            {
                                RGBValues[2] = Color.FromArgb(0, 2 / 3 * RGBValues[0].R + 1 / 3 * RGBValues[1].R, 2 / 3 * RGBValues[0].G + 1 / 3 * RGBValues[1].G, 2 / 3 * RGBValues[0].B + 1 / 3 * RGBValues[1].B);
                                RGBValues[3] = Color.FromArgb(0, 1 / 3 * RGBValues[0].R + 2 / 3 * RGBValues[1].R, 1 / 3 * RGBValues[0].G + 2 / 3 * RGBValues[1].G, 1 / 3 * RGBValues[0].B + 2 / 3 * RGBValues[1].B);
                            }
                            else
                            {
                                Debug.Print("Weird Values found in file...");
                            }

                            for (int y1 = 0; y1 <= 3; y1++)
                            {
                                for (int x1 = 0; x1 <= 3; x1++)
                                {
                                    int ci = ColorLUT[3 - y1, 3 - x1];
                                    int al = AlphaMap[y1, x1];
                                    bmp.SetPixel(x + x1, y + y1, Color.FromArgb(al, RGBValues[ci].R, RGBValues[ci].G, RGBValues[ci].B));
                                }
                            }
                        }
                    }
                }
                else if ( mHeader.compression == 2 && mHeader.alpha_bits == 8 && mHeader.alpha_type == 7)
                {
                    // DXT5
                    for (int y = 0; y < height; y += 4)
                    {
                        for (int x = 0; x < width; x += 4)
                        {
                            byte a0 = mReader.ReadByte();
                            byte a1 = mReader.ReadByte();
                            byte[] _AlphaLUT = new byte[] { 
                                mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte(), 
                                mReader.ReadByte(), mReader.ReadByte(), mReader.ReadByte() };
                            short[] _RGBValues = new short[2] { mReader.ReadInt16(), mReader.ReadInt16() };
                            int _ColorLUT = mReader.ReadInt32();

                            int[,] ColorLUT = new int[4, 4] { { (_ColorLUT >> 30 & 3), (_ColorLUT >> 28 & 3), (_ColorLUT >> 26 & 3), (_ColorLUT >> 24 & 3) }, { (_ColorLUT >> 22 & 3), (_ColorLUT >> 20 & 3), (_ColorLUT >> 18 & 3), (_ColorLUT >> 16 & 3) }, { (_ColorLUT >> 14 & 3), (_ColorLUT >> 12 & 3), (_ColorLUT >> 10 & 3), (_ColorLUT >> 8 & 3) }, { (_ColorLUT >> 6 & 3), (_ColorLUT >> 4 & 3), (_ColorLUT >> 2 & 3), (_ColorLUT & 3) } };

                            // the LUT looks like this (I think):
                            // b0        |b1         |b2         |b3        |b4         |b5        
                            // 012 345 67|0 123 456 7|01 234 567 |012 345 67|0 123 456 7|01 234 567
                            // a00 a01 a0|2 a03 a10 a|11 a12 a13 |a20 a21 a2|2 a23 a30 a|31 a32 a33

                            int[,] AlphaLUT = new int[4, 4];
                            AlphaLUT[0, 0] = _AlphaLUT[0] >> 0 & 7;
                            AlphaLUT[0, 1] = _AlphaLUT[0] >> 3 & 7;
                            AlphaLUT[0, 2] = (_AlphaLUT[0] >> 6 & 3) | (_AlphaLUT[1] << 2 & 4);
                            AlphaLUT[0, 3] = _AlphaLUT[1] >> 1 & 7;
                            AlphaLUT[1, 0] = _AlphaLUT[1] >> 4 & 7;
                            AlphaLUT[1, 1] = (_AlphaLUT[1] >> 7 & 1) | (_AlphaLUT[2] << 1 & 6);
                            AlphaLUT[1, 2] = _AlphaLUT[2] >> 2 & 7;
                            AlphaLUT[1, 3] = _AlphaLUT[2] >> 5 & 7;

                            AlphaLUT[2, 0] = _AlphaLUT[3] >> 0 & 7;
                            AlphaLUT[2, 1] = _AlphaLUT[3] >> 3 & 7;
                            AlphaLUT[2, 2] = (_AlphaLUT[3] >> 6 & 3) | (_AlphaLUT[4] << 2 & 4);
                            AlphaLUT[2, 3] = _AlphaLUT[4] >> 1 & 7;
                            AlphaLUT[3, 0] = _AlphaLUT[4] >> 4 & 7;
                            AlphaLUT[3, 1] = (_AlphaLUT[4] >> 7 & 1) | (_AlphaLUT[5] << 1 & 6);
                            AlphaLUT[3, 2] = _AlphaLUT[5] >> 2 & 7;
                            AlphaLUT[3, 3] = _AlphaLUT[5] >> 5 & 7;


                            Color[] RGBValues = new Color[4];
                            RGBValues[0] = Color.FromArgb(0, _RGBValues[0] >> 8 & 255, _RGBValues[0] >> 3 & 255, _RGBValues[0] << 3 & 255);
                            RGBValues[1] = Color.FromArgb(0, _RGBValues[1] >> 8 & 255, _RGBValues[1] >> 3 & 255, _RGBValues[1] << 3 & 255);

                            if (_RGBValues[0] > _RGBValues[1])
                            {
                                RGBValues[2] = Color.FromArgb(0, 2 / 3 * RGBValues[0].R + 1 / 3 * RGBValues[1].R, 2 / 3 * RGBValues[0].G + 1 / 3 * RGBValues[1].G, 2 / 3 * RGBValues[0].B + 1 / 3 * RGBValues[1].B);
                                RGBValues[3] = Color.FromArgb(0, 1 / 3 * RGBValues[0].R + 2 / 3 * RGBValues[1].R, 1 / 3 * RGBValues[0].G + 2 / 3 * RGBValues[1].G, 1 / 3 * RGBValues[0].B + 2 / 3 * RGBValues[1].B);
                            }
                            else
                            {
                                Debug.Print("Weird Values found in file");
                            }

                            byte[] AlphaValues = new byte[8];
                            AlphaValues[0] = a0;
                            AlphaValues[1] = a1;
                            if (AlphaValues[0] > AlphaValues[1])
                            {
                                AlphaValues[2] = (byte)((6f * a0 + 1f * a1) / 7f);
                                AlphaValues[3] = (byte)((5f * a0 + 2f * a1) / 7f);
                                AlphaValues[4] = (byte)((4f * a0 + 3f * a1) / 7f);
                                AlphaValues[5] = (byte)((3f * a0 + 4f * a1) / 7f);
                                AlphaValues[6] = (byte)((2f * a0 + 5f * a1) / 7f);
                                AlphaValues[7] = (byte)((1f * a0 + 6f * a1) / 7f);
                            }
                            else
                            {
                                AlphaValues[2] = (byte)((4f * a0 + 1f * a1) / 5f);
                                AlphaValues[3] = (byte)((3f * a0 + 2f * a1) / 5f);
                                AlphaValues[4] = (byte)((2f * a0 + 3f * a1) / 5f);
                                AlphaValues[5] = (byte)((1f * a0 + 4f * a1) / 5f);
                                AlphaValues[6] = 0;
                                AlphaValues[7] = 255;
                            }

                            for (int y1 = 0; y1 <= 3; y1++)
                            {
                                for (int x1 = 0; x1 <= 3; x1++)
                                {
                                    int ci = ColorLUT[3 - y1, 3 - x1];
                                    int al = AlphaValues[AlphaLUT[y1, x1]];
                                    bmp.SetPixel(x + x1, y + y1, Color.FromArgb(al, RGBValues[ci].R, RGBValues[ci].G, RGBValues[ci].B));
                                }
                            }
                        }
                    }
                }
                else
                {
                    Debug.Print("File with unknown properties found:" + mHeader.ToString());
                    //we don't support this yet...
                    break;
                }

                mBitmaps.Add(bmp);
            }

        }

        private void ReadHeader()
        {
            mHeader.ident = new string(mReader.ReadChars(4));
            if (mHeader.ident != "BLP2")
            {
                return;
            }
            mHeader.type = mReader.ReadInt32();
            mHeader.compression = mReader.ReadByte();
            mHeader.alpha_type = mReader.ReadByte();
            mHeader.alpha_bits = mReader.ReadByte();
            mHeader.has_mips = mReader.ReadByte();
            mHeader.width = mReader.ReadInt32();
            mHeader.height = mReader.ReadInt32();
            for (int i = 0; i < 16; i++)
            {
                mHeader.mipmap_offsets[i] = mReader.ReadInt32();
            }
            for (int i = 0; i < 16; i++)
            {
                mHeader.mipmap_lengths[i] = mReader.ReadInt32();
            }
        }
             
        public Bitmap this[int i]
        {
            get
            {
                if (i > mBitmaps.Count - 1 || i < 0)
                {
                    return null;
                }
                return mBitmaps[i];
            }
        }
    }
}

